/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include <algorithm>
#include <protect_engines/kubernetes/common/KubeTaskLabels.h>
#include "Transformer.h"
KUBERNETES_PLUGIN_NAMESPACE_BEGIN
using VirtPlugin::VolInfo;

namespace KubeHelper {
std::optional<KubernetesApi> GetKubernetesApiFromAppEnv(const std::string &appEnvAuthExtend)
{
    KubernetesPlugin::AppEnvExtendInfo appEnvExtendInfo;
    if (!Module::JsonHelper::JsonStringToStruct(appEnvAuthExtend, appEnvExtendInfo)) {
        ERRLOG("appEnv.auth.extendInfo wrong format.");
        return std::nullopt;
    }
    auto[isInit, kubeConfig] =
            KubernetesPlugin::KubeConfig::Create(appEnvExtendInfo.config);
    if (!isInit) {
        ERRLOG("appEnv.auth.extendInfo.config wrong format.");
        return std::nullopt;
    }

    KubernetesPlugin::KubeClient realClient;
    KubernetesPlugin::KubernetesApi kubernetesApi(realClient, kubeConfig);
    return kubernetesApi;
}

std::optional<std::vector<StorageParam>> GetStorageParamVecFromAppEnv(const std::string &appEnvAuthExtend)
{
    KubernetesPlugin::AppEnvExtendInfo appEnvExtendInfo;
    if (!Module::JsonHelper::JsonStringToStruct(appEnvAuthExtend, appEnvExtendInfo)) {
        ERRLOG("appEnv.auth.extendInfo wrong format.");
        return std::nullopt;
    }
    Json::Value storageValue;
    if (!Module::JsonHelper::JsonStringToJsonValue(appEnvExtendInfo.storages, storageValue)) {
        ERRLOG("appEnvExtendInfo.storages wrong format.");
        return std::nullopt;
    }
    std::vector<StorageParam> storageVector = {};
    for (const auto &item : storageValue) {
        StorageParam storageParam;
        if (!Module::JsonHelper::JsonStringToStruct(item.toStyledString(), storageParam)) {
            ERRLOG("convert Failed for storage item: %s", item.toStyledString().c_str());
            return std::nullopt;
        }
        storageVector.push_back(storageParam);
    }
    return storageVector;
}

std::pair<int32_t, std::shared_ptr<StorageClient>> GetStorageClientFromStorageParam(const StorageParam &storageParam)
{
    KubernetesPlugin::AccessAuthParam accessAuthParam(storageParam.username, storageParam.password, "0");
    auto[initRet, storageClient]= StorageClient::Create(storageParam.ip, storageParam.port, accessAuthParam,
        storageParam.ipList);
    return std::make_pair(initRet, storageClient);
}

std::pair<int32_t, std::shared_ptr<StorageClient>> CheckStorageClientFromStorageParam(
    KubernetesPlugin::AccessAuthParam accessAuthParam, const StorageParam &storageParam)
{
    auto[initRet, storageClient]= StorageClient::Create(storageParam.ip, storageParam.port, accessAuthParam,
        storageParam.ipList, false);
    return std::make_pair(initRet, storageClient);
}

std::optional<StorageParam> GetStorageParamFromVol(std::vector<StorageParam> storageParamList,
                                                   std::string volExtendInfo)
{
    for (const StorageParam &item : storageParamList) {
        std::vector<std::string> storageInputUrl {};
        item.GetStorageUrl(storageInputUrl);
        for (const std::string &storageUrl : storageInputUrl) {
            if (volExtendInfo.find(storageUrl) != std::string::npos) {
                return item;
            }
        }
    }
    ERRLOG("We can't find any item for volExtendInfo: %s", volExtendInfo.c_str());
    return std::nullopt;
}

void FulfillVolInfo(VolInfo &volInfo,
                    const StorageDeviceInfo &storageDeviceInfo,
                    const LunInfoData &lunInfoData,
                    const StorageParam &storageParam)
{
    FulfillVolInfo(volInfo, lunInfoData);
    volInfo.m_type = storageDeviceInfo.productMode;
    volInfo.m_datastore.m_name = storageDeviceInfo.name;
    volInfo.m_datastore.m_moRef = storageDeviceInfo.sn;
    volInfo.m_datastore.m_ip = storageParam.ip;
    volInfo.m_datastore.m_ipList = storageParam.ipList;
    volInfo.m_datastore.m_port = std::to_string(storageParam.port);
    volInfo.m_datastore.m_type = storageDeviceInfo.productMode;
    volInfo.m_datastore.m_poolId = lunInfoData.m_parentId;
}

void FulfillVolInfo(VolInfo &volInfo, const LunInfoData &lunInfoData)
{
    volInfo.m_moRef = lunInfoData.m_id;
    volInfo.m_uuid = lunInfoData.m_wwn;
    volInfo.m_name = lunInfoData.m_name;
    try {
        uint64_t sectorSize = std::stoull(lunInfoData.m_sectorSize);
        uint64_t capacity = std::stoull(lunInfoData.m_capacity);
        volInfo.m_volSizeInBytes = sectorSize * capacity;
    } catch (std::exception &e) {
        ERRLOG("NULL lunInfoData.m_sectorSize: %s lunInfo.m_capacity: %s",
            lunInfoData.m_sectorSize.c_str(), lunInfoData.m_capacity.c_str());
        volInfo.m_volSizeInBytes = 0;
    }

    VirtPlugin::VolumeDSExtendInfo dsExtend;
    dsExtend.m_volWwn = lunInfoData.m_wwn;
    dsExtend.m_volId = lunInfoData.m_id;
    dsExtend.m_volName = lunInfoData.m_name;
    Module::JsonHelper::StructToJsonString(dsExtend, volInfo.m_datastore.m_extendInfo);
}

std::optional<StateFulSet> GetStateFulSetFromExtendInfo(const std::string &appExtend)
{
    StateFulSetExtend stsExtendInfo;
    if (!Module::JsonHelper::JsonStringToStruct(appExtend, stsExtendInfo)) {
        ERRLOG("appEnv.auth.extendInfo wrong format.");
        return std::nullopt;
    }
    StateFulSet targetStateFulSet;
    if (!Module::JsonHelper::JsonStringToStruct(stsExtendInfo.sts, targetStateFulSet)) {
        ERRLOG("appEnv.auth.extendInfo wrong format.");
        return std::nullopt;
    }
    return targetStateFulSet;
}

std::string GetPvcNameWithOutPodInfo(const std::string pvcName)
{
    if (pvcName.length() <= LEN_POD_INFO) {
        return pvcName;
    }
    return pvcName.substr(0, pvcName.length()-LEN_POD_INFO);
}

std::optional<VolInfo> GenTargetVolFromInfos(const ApplicationResource &volResource,
                                             const StorageParam &storageParam,
                                             std::shared_ptr<StorageClient> storageClient)
{
    // 在 volResource.extendInfo中获取目标pv信息
    PvExtend pvExtendInfo;
    if (!Module::JsonHelper::JsonStringToStruct(volResource.extendInfo, pvExtendInfo)) {
        ERRLOG("volResource. wrong format.");
        return std::nullopt;
    }
    Pv pv;
    if (!Module::JsonHelper::JsonStringToStruct(pvExtendInfo.pv, pv)) {
        ERRLOG("appEnv.auth.extendInfo wrong format.");
        return std::nullopt;
    }
    DBGLOG("Source volume: %s", pvExtendInfo.pv.c_str());
    DBGLOG("Target volume: %s wwn: %s", volResource.name.c_str(), volResource.id.c_str());
    if (GetPvcNameWithOutPodInfo(pv.pvcName) == GetPvcNameWithOutPodInfo(volResource.name)) {
        VolInfo targetVol;
        LunInfoData lunInfoData;
        int ret = SUCCESS;
        std::tie(ret, lunInfoData) = storageClient->GetLunInfoData(pv.lunName);
        if (ret != SUCCESS) {
            return std::nullopt;
        }
        StorageDeviceInfo deviceInfo;
        std::tie(ret, deviceInfo) = storageClient->GetDeviceBaseInfo();
        if (ret != SUCCESS) {
            return std::nullopt;
        }
        KubeHelper::FulfillVolInfo(targetVol, deviceInfo, lunInfoData, storageParam);
        return targetVol;
    }
    ERRLOG("No match volumeName for source pvc: %s target pvc: %s.", volResource.name.c_str(), pv.pvcName.c_str());
    return std::nullopt;
}

bool CheckKubeExecRetContainKey(const std::string &kubeExecRet)
{
    std::istringstream f(kubeExecRet);
    std::string s;
    while (std::getline(f, s, '\n')) {
        if (s.find("[feedback]status=normal") != std::string::npos) {
            return true;
        }
    }
    return false;
}

std::optional<std::string> GetScriptFromExtendInfo(const std::string &appExtend, ScriptType scriptType)
{
    ScriptExtendInfo scriptExtendInfo;
    if (!Module::JsonHelper::JsonStringToStruct(appExtend, scriptExtendInfo)) {
        ERRLOG("scriptExtendInfo transForm failed.");
        return std::nullopt;
    }
    switch (scriptType) {
        case ScriptType::PRE_SCRIPT:
            return scriptExtendInfo.m_preScript;
        case ScriptType::POST_SCRIPT:
            return scriptExtendInfo.m_postScript;
        case ScriptType::FAILED_SCRIPT:
            return scriptExtendInfo.m_failedScript;
    }
}


std::string ConvertTimeStamp2TimeStr(time_t timeStamp)
{
    size_t maxSize = 80;
    struct tm *timeinfo = nullptr;
    char buffer[maxSize];
    timeinfo = localtime(&timeStamp);
    strftime(buffer, maxSize, "%Y-%m-%d %H:%M:%S", timeinfo);
    printf("%s\n", buffer);
    return std::string(buffer);
}

std::optional<StorageParam> FindMatchSnStorage(const std::vector<StorageParam> &storages, const std::string &sn)
{
    StorageParam storage;
    storage.sn = sn;

    auto it = std::find(storages.begin(), storages.end(), storage);
    if (it == storages.end()) {
        ERRLOG("No usable storage device param found in backup param, required sn: %s", sn.c_str());
        return std::nullopt;
    }
    return *it;
}

bool FindStorageIdInMap(std::map <std::string, std::set<std::string>> &poolMap, const std::string &poolId,
    const std::string &sn)
{
    auto it = poolMap.find(sn);
    if (it == poolMap.end()) {
        ERRLOG("No usable storage device param found, required sn: %s", sn.c_str());
        return false;
    }
    if (it->second.count(poolId) == 0) {
        ERRLOG("No usable pool if param found, required id: %s", poolId.c_str());
        return false;
    }
    return true;
}

std::unordered_map<std::string, std::shared_ptr<StorageClient>> FindMatchSnapInfoStorageClients(
    const VirtPlugin::SnapshotInfo &snapshotInfo,
    const std::shared_ptr<AppProtect::BackupJob> &backupParam)
{
    std::unordered_map<std::string, std::shared_ptr<StorageClient>> storageClients = {};
    if (backupParam == nullptr) {
        ERRLOG("backupParam is nullptr.");
        return storageClients;
    }
    auto storagesOpt = KubeHelper::GetStorageParamVecFromAppEnv(backupParam->protectEnv.auth.extendInfo);
    if (!storagesOpt) {
        ERRLOG("Generate storages from backup param fail.");
        return storageClients;
    }

    std::set<std::string> storageSnSet = {};
    for (const auto &volSnapInfo : snapshotInfo.m_volSnapList) {
        std::string storageSn = volSnapInfo.m_datastore.m_moRef;
        if (storageSn.empty()) {
            ERRLOG("Failed to get sn from pre snapshot list, task ID: %s", backupParam->jobId.c_str());
            return storageClients;
        }
        storageSnSet.insert(storageSn);
    }
    for (const auto &storageSn : storageSnSet) {
        auto storageOpt = KubeHelper::FindMatchSnStorage(storagesOpt.value(), storageSn);
        if (!storageOpt.has_value()) {
            ERRLOG("No usable storage device param found in backup param, required sn: %s, task ID: %s",
                storageSn.c_str(), backupParam->jobId.c_str());
            return storageClients;
        }
        StorageParam storage = storageOpt.value();
        auto[initRet, storageClient] = KubeHelper::GetStorageClientFromStorageParam(storage);
        if (initRet != SUCCESS) {
            ERRLOG("Storage auth fail, ip: %s, port: %d, response code: %d task ID: %s", storage.ip.c_str(),
                storage.port, initRet, backupParam->jobId.c_str());
            return storageClients;
        }
        storageClients[storageSn] = storageClient;
    }
    return storageClients;
}

std::pair<bool, std::string> CheckPodPvsInSameStorage(BatchSnapshotParam &createParam)
{
    std::set<std::string> storageUrlSet = {};
    for (const auto &pv: createParam.m_pvs) {
        storageUrlSet.insert(pv.storageUrl);
    }

    bool isSameStorage = (storageUrlSet.size() == 1);
    std::string storageUrl = *storageUrlSet.begin();
    return std::make_pair(isSameStorage, storageUrl);
}

std::pair<int32_t, VolInfo> GenerateVolInfo(PerSnapshotParam snapshotInfo,
                                            std::shared_ptr<AppProtect::BackupJob> &backupParam,
                                            BatchSnapshotParam &batchSnapshotParam)
{
    VolInfo volInfo;
    volInfo.m_moRef = snapshotInfo.m_lunInfoData.m_id;
    volInfo.m_uuid = snapshotInfo.m_lunInfoData.m_wwn;
    volInfo.m_name = snapshotInfo.m_lunInfoData.m_name;
    volInfo.m_type = snapshotInfo.m_lunInfoData.m_subtype;
    if (backupParam == nullptr) {
        ERRLOG("backupParam is nullptr.");
        return std::make_pair(FAILED, volInfo);
    }
    volInfo.m_vmMoRef = backupParam->protectObject.id;

    uint64_t sectorSize = std::stoull(snapshotInfo.m_lunInfoData.m_sectorSize);
    uint64_t capacity = std::stoull(snapshotInfo.m_lunInfoData.m_capacity);
    volInfo.m_volSizeInBytes = sectorSize * capacity;

    DatastoreInfo datastoreInfo;
    datastoreInfo.m_name = batchSnapshotParam.m_storageDeviceInfo.name;
    datastoreInfo.m_moRef = batchSnapshotParam.m_storageDeviceInfo.sn;
    datastoreInfo.m_type = batchSnapshotParam.m_storageDeviceInfo.productMode;
    datastoreInfo.m_ip = batchSnapshotParam.m_storage.ip;
    datastoreInfo.m_port = std::to_string(batchSnapshotParam.m_storage.port);
    datastoreInfo.m_poolId = snapshotInfo.m_lunInfoData.m_parentId;
    VirtPlugin::VolumeDSExtendInfo dsExtend;
    dsExtend.m_volWwn = snapshotInfo.m_lunInfoData.m_wwn;
    dsExtend.m_volId = snapshotInfo.m_lunInfoData.m_id;
    dsExtend.m_volName = snapshotInfo.m_lunInfoData.m_name;
    Module::JsonHelper::StructToJsonString(dsExtend, datastoreInfo.m_extendInfo);
    volInfo.m_datastore = datastoreInfo;

    // metadata包括备份LUN信息、快照信息、pv信息
    std::string volExtendInfo;
    if (!Module::JsonHelper::StructToJsonString(snapshotInfo, volExtendInfo)) {
        ERRLOG("SnapshotInfo transfer to json string err.");
        return std::make_pair(FAILED, volInfo);
    }
    volInfo.m_extendInfo = volExtendInfo;

    std::string volMetadata;
    if (!Module::JsonHelper::StructToJsonString(volInfo, volMetadata)) {
        ERRLOG("volInfo transfer to json string err.");
        return std::make_pair(FAILED, volInfo);
    };
    volInfo.m_metadata = volMetadata;
    return std::make_pair(SUCCESS, volInfo);
}

std::string JoinLunNames(const BatchSnapshotParam &createParam)
{
    std::string param;
    for (auto index = createParam.m_pvs.begin(); index != createParam.m_pvs.end(); ++index) {
        param += (*index).lunName;
        if (index + 1 != createParam.m_pvs.end()) {
            param += ",";
        }
    }
    return param;
}
std::string FindLostVolumeNamesInPvs(const std::vector<std::string> &volumeNames, const std::vector<Pv> &pvs)
{
    std::string result;
    for (const auto &volumeName : volumeNames) {
        bool pvsContainVolName = false;
        auto isSameVolumeName = [&volumeName](Pv pv) { return pv.volumeName == volumeName; };
        if (std::find_if(pvs.begin(), pvs.end(), isSameVolumeName) != pvs.end()) {
            pvsContainVolName = true;
        }
        if (!pvsContainVolName) {
            if (result.empty()) {
                result = volumeName;
            } else {
                result += "," + volumeName;
            }
        }
    }
    return result;
}

namespace {
ScriptRetType GetRetTypeFromExecResult(const std::string& execResult)
{
    ScriptRetType retType = ScriptRetType::SCRIPT_SUCCESS;
    if (execResult.find("OCI runtime exec failed: exec failed:") != std::string::npos) {
        retType = ScriptRetType::SCRIPT_FAILED;
    }
    // 目前报无权限、或者格式错误、未找到脚本时，给出的提示也包含AOCI runtime exec failed的信息
    // 无权限返回的提示信息如下：cmdResult: ^AOCI runtime exec failed: exec failed: container_linux.go:380:
    // starting container process caused: exec format error: unknown
    if (execResult.find("permission denied:") != std::string::npos) {
        retType = ScriptRetType::SCRIPT_NO_PERMIT;
    } else if (execResult.find("exec format error:") != std::string::npos) {
        retType = ScriptRetType::SCRIPT_FORMAT_ERR;
    } else if (execResult.find("no such file or directory:") != std::string::npos) {
        retType = ScriptRetType::SCRIPT_NOT_FOUND;
    }
    return retType;
}

std::map<std::pair<TaskStage, ScriptRetType>, std::string> JOB_LABEL_MAP = {
    { std::make_pair(Pre_Task, SCRIPT_SUCCESS), PRE_SCRIPT_EXEC_SUCCESS_LABEL },
    { std::make_pair(Pre_Task, SCRIPT_FAILED), PRE_SCRIPT_EXEC_FAIL_LABEL },
    { std::make_pair(Pre_Task, SCRIPT_NO_PERMIT), PRE_SCRIPT_NO_PERMISSION_EXEC_LABEL },
    { std::make_pair(Pre_Task, SCRIPT_FORMAT_ERR), PRE_SCRIPT_FORMAT_ERROR_LABEL },
    { std::make_pair(Pre_Task, SCRIPT_NOT_FOUND), PRE_SCRIPT_NOT_FOUND_LABEL },

    { std::make_pair(Post_Task, SCRIPT_SUCCESS), POST_SCRIPT_EXEC_SUCCESS_LABEL },
    { std::make_pair(Post_Task, SCRIPT_FAILED), POST_SCRIPT_EXEC_FAIL_LABEL },
    { std::make_pair(Post_Task, SCRIPT_NO_PERMIT), POST_SCRIPT_NO_PERMISSION_EXEC_LABEL },
    { std::make_pair(Post_Task, SCRIPT_FORMAT_ERR), POST_SCRIPT_FORMAT_ERROR_LABEL },
    { std::make_pair(Post_Task, SCRIPT_NOT_FOUND), POST_SCRIPT_NOT_FOUND_LABEL },

    { std::make_pair(Fail_Task, SCRIPT_SUCCESS), FAILED_SCRIPT_EXEC_SUCCESS_LABEL },
    { std::make_pair(Fail_Task, SCRIPT_FAILED), FAILED_SCRIPT_EXEC_FAIL_LABEL },
    { std::make_pair(Fail_Task, SCRIPT_NO_PERMIT), BACKUP_FAILED_NO_PERMISSION_EXEC_FAILED_SCRIPT_LABEL },
    { std::make_pair(Fail_Task, SCRIPT_FORMAT_ERR), BACKUP_FAILED_EXEC_SCRIPT_FORMAT_ERROR_LABEL },
    { std::make_pair(Fail_Task, SCRIPT_NOT_FOUND), BACKUP_FAILED_EXEC_SCRIPT_NOT_FOUND_LABEL }
};
}

std::pair<bool, std::string> GetLabelByExecKubeResult(const std::string &excResult, const TaskStage &stage)
{
    std::string label;
    ScriptRetType scriptRetType = GetRetTypeFromExecResult(excResult);
    std::pair<TaskStage, ScriptRetType> keyForJobLabel = std::make_pair(stage, scriptRetType);

    auto it = JOB_LABEL_MAP.find(keyForJobLabel);
    if (it != JOB_LABEL_MAP.end()) {
        label = it->second;
    } else {
        label = "";
    }
    if (scriptRetType == SCRIPT_SUCCESS) {
        INFOLOG("Exec script info: %s, Exec result: %s", label.c_str(), excResult.c_str());
    } else {
        ERRLOG("Exec script info: %s, Exec result: %s", label.c_str(), excResult.c_str());
    }
    return std::make_pair(scriptRetType == SCRIPT_SUCCESS, label);
}

std::string GetKubePreScriptExecResult(const std::string &preScriptExecRet)
{
    std::string execResult;
    std::istringstream f(preScriptExecRet);
    std::string s;
    while (std::getline(f, s, '\n')) {
        auto pos = s.find("[feedback]status=");
        if (pos != std::string::npos) {
            execResult = s.substr(pos);
            break;
        }
    }
    return execResult;
}
}
KUBERNETES_PLUGIN_NAMESPACE_END